namespace NHCodeFirst
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using NHibernate;
    using NHibernate.Engine;

    public class DbContext
    {
        private static readonly object mutex = "";
        internal readonly List<object> AddedItems = new List<object>();
        internal readonly List<object> DeletedItems = new List<object>();
        private readonly Dictionary<Type, object> loadedSets = new Dictionary<Type, object>();

        protected DbContext()
        {
            var properties = GetType().GetProperties();
            var dbSets = (from property in properties
                          where property.PropertyType.IsGenericType
                          let genericTypeDefinition = property.PropertyType.GetGenericTypeDefinition()
                          where typeof(DbSet<>) == genericTypeDefinition
                          select property).ToList();
            lock (mutex)
            {
                if (NHibernateConfiguration.Mappings == null)
                {
                    NHibernateConfiguration.Mappings = new List<string>();
                    var fluentMapper = new FluentMapper.FluentMapper();
                    FluentMap(fluentMapper);
                    foreach (var type in dbSets.Select(dbSet => dbSet.PropertyType.GetGenericArguments()[0]))
                        EntityInspector.Inspect(type);
                    foreach (var hibernateMapping in NHibernateConfiguration.MappingModels)
                        NHibernateConfiguration.Mappings.Add(Helpers.HbmGenerator.GenerateHbm(hibernateMapping.Value));
                    Helpers.NHibernateHelper.BuildSessionFactory(fluentMapper.Configuration);
                }
                Session = Helpers.NHibernateHelper.GetCurrentSession();
            }
        }

        public ISession Session { get; private set; }
        public ITransaction Transaction { get; private set; }

        public T Find<T>(object id) where T : class
        {
            var find = Session.Get<T>(id);
            if (find == null)
                throw new ArgumentException("identifier for entity " + typeof (T).Name + " not exists.");
            return find;
        }

        public DbSet<T> Set<T>() where T : class
        {
            var dbsetType = typeof(DbSet<T>);
            if (!loadedSets.ContainsKey(dbsetType))
            {
                var set = new DbSet<T>(this);
                loadedSets.Add(dbsetType, set);
            }
            return (DbSet<T>)loadedSets[dbsetType];
        }

        public void SaveChanges()
        {
            foreach (var addedItem in AddedItems)
                Session.Save(addedItem);
            foreach (var deletedItem in DeletedItems)
                Session.Delete(deletedItem);
            foreach (DictionaryEntry entityEntry in Session.GetSessionImplementation().PersistenceContext.EntityEntries)
            {
                var entity = entityEntry.Key;
                if (((EntityEntry)entityEntry.Value).Status == Status.Deleted)
                    continue;
                Session.Update(entity);
            }
            AddedItems.Clear();
            DeletedItems.Clear();
            if (Transaction != null)
                Transaction.Commit();
        }

        public void Add<T>(T obj) where T : class 
        {
            Set<T>().Add(obj);
        }

        public void Remove<T>(T obj) where T : class
        {
            Set<T>().Remove(obj);
        }

        protected virtual void FluentMap(FluentMapper.FluentMapper mapper)
        {            
        }
    }
}